﻿using System;
using System.Xml;
using System.Text;
using System.Reflection;
using System.Threading;
using Microsoft.SharePoint;
using System.Data.SqlClient;
using System.Collections.Generic;
using System.Collections.Specialized;
using Microsoft.SharePoint.Security;
using System.Security.Permissions;

namespace Voodoo.SharePoint.ReadUnreadMarks
{
    /// <summary>
    /// a ckass with information to locate a specific item 
    /// </summary>
    internal class DocumentItemLocator
    {
        public Guid SiteId = Guid.Empty;
        public Guid WebId = Guid.Empty;
        public Guid ListId = Guid.Empty;
        public Guid ItemId = Guid.Empty;
        public string Path = string.Empty;
        public string UserName = string.Empty;

        public string Key
        {
            get
            {
                return SiteId.ToString() + WebId.ToString() + ListId.ToString() + ItemId.ToString() + UserName;
            }
        }
    }

    //This class is a collection of utilities that bypass or enhance the
    //SharePoint api in ways that are not supported by MS, for example
    //we disable event firing for the current thread, or read/write directly
    //to content columns. Most of this was figured out by snooping around in 
    //the SQL stored procs, or peeking at the assembly code using .NET Reflector
    //the method names are pretty self explanatory 
    public class UtilityMethods
    {


        #region Declarations
        //Ideally these should be stored procedures but thats probably going
        //to far,seeing as how we are in the middle of the WSS content database
        private const string SQL_READ_NVARCHARMAX = "SELECT %COLNAME% FROM AllUserData WHERE tp_ListId = '%LISTID%' AND tp_ID = %ITEMID%";
        private const string SQL_UPDATE_NVARCHARMAX = "UPDATE AllUserData SET %COLNAME% ='%SMALLTEXTSTRING%' WHERE tp_ListId = '%LISTID%' AND tp_ID = %ITEMID%";
        private const string SQL_APPEND_NVARCHARMAX = "UPDATE AllUserData SET %COLNAME% .WRITE('%SMALLTEXTSTRING%',%OFFSET%,%SMALLTEXTLENGTH%) WHERE tp_ListId = '%LISTID%' AND tp_ID = %ITEMID%";
        private const string SQL_CONCAT_NVARCHARMAX = "UPDATE AllUserData SET %COLNAME% = ISNULL ( %COLNAME% , '.' )  +'%SMALLTEXTSTRING%' WHERE tp_ListId = '%LISTID%' AND tp_ID = %ITEMID%";
        private const string SQL_REMOVE_NVARCHARMAX = "UPDATE AllUserData SET %COLNAME% = REPLACE(%COLNAME%,'%SMALLTEXTSTRING%','') WHERE tp_ListId = '%LISTID%' AND tp_ID = %ITEMID%";

        public const string WEB_CONFIG_OWNER = "Voodoo.SharePoint.ReadUnreadMarks";
        #endregion

        #region RemoveTextFromSharePointNVARCHARMAXUserDataColumn
        public static void RemoveTextFromSharePointNVARCHARMAXUserDataColumn(string textToRemove, Guid siteId, Guid listId, int itemId, SPField sharePointField)
        {
            XmlDocument schemaXML = new XmlDocument();
            schemaXML.LoadXml(sharePointField.SchemaXml);
            XmlNode fieldNode = schemaXML.SelectSingleNode("/Field");
            XmlAttribute columnNameAttribute = fieldNode.Attributes["ColName"];
            RemoveTextFromSharePointNVARCHARMAXUserDataColumn(textToRemove, siteId, listId, itemId, columnNameAttribute.InnerText);
        }

        public static void RemoveTextFromSharePointNVARCHARMAXUserDataColumn(string textToRemove, Guid siteId, Guid listId, int itemId, string ntextColumnName)
        {
            if (textToRemove.Length > 512 || -1 != textToRemove.IndexOf("'") || string.IsNullOrEmpty(textToRemove))
            {
                throw new ArgumentException("Invald textToRemove value or format");
            }
            using (SPSite currentSite = new SPSite(siteId))
            {

                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    string dbConnectionString = currentSite.ContentDatabase.DatabaseConnectionString;
                    StringBuilder sqlCommandText = new StringBuilder();
                    sqlCommandText.AppendLine("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
                    sqlCommandText.AppendLine("BEGIN TRANSACTION");

                    string removeMarkSQL = SQL_REMOVE_NVARCHARMAX.Replace("%COLNAME%", ntextColumnName).Replace("%SMALLTEXTSTRING%", textToRemove);
                    removeMarkSQL = removeMarkSQL.Replace("%LISTID%", listId.ToString().ToUpper()).Replace("%ITEMID%", itemId.ToString());
                    sqlCommandText.AppendLine(removeMarkSQL);

                    sqlCommandText.AppendLine("COMMIT TRANSACTION");
                    string commandText = sqlCommandText.ToString();
                    using (SqlConnection sqlConn = new SqlConnection(dbConnectionString))
                    {
                        sqlConn.Open();
                        using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConn))
                        {
                            sqlCommand.ExecuteNonQuery();
                        }
                    }
                });
            }
        }
        #endregion

        #region SetTextExactToSharePointNVARCHARMAXUserDataColumn
        public static void SetTextExactToSharePointNVARCHARMAXUserDataColumn(string exactText, Guid siteId, Guid listId, int itemId, SPField sharePointField)
        {
            XmlDocument schemaXML = new XmlDocument();
            schemaXML.LoadXml(sharePointField.SchemaXml);
            XmlNode fieldNode = schemaXML.SelectSingleNode("/Field");
            XmlAttribute columnNameAttribute = fieldNode.Attributes["ColName"];
            SetTextExactToSharePointNVARCHARMAXUserDataColumn(exactText, siteId, listId, itemId, columnNameAttribute.InnerText);
        }

        public static void SetTextExactToSharePointNVARCHARMAXUserDataColumn(string exactText, Guid siteId, Guid listId, int itemId, string ntextColumnName)
        {
            if (exactText.Length > 512 || -1 != exactText.IndexOf("'"))
            {
                throw new ArgumentException("Invald exactText value or format");
            }
            using (SPSite currentSite = new SPSite(siteId))
            {

                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    string dbConnectionString = currentSite.ContentDatabase.DatabaseConnectionString;

                    StringBuilder sqlCommandText = new StringBuilder();
                    sqlCommandText.AppendLine("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
                    sqlCommandText.AppendLine("BEGIN TRANSACTION");

                    string markText = SQL_UPDATE_NVARCHARMAX.Replace("%COLNAME%", ntextColumnName).Replace("%SMALLTEXTSTRING%", exactText);
                    markText = markText.Replace("%ITEMID%", itemId.ToString()).Replace("%LISTID%", listId.ToString().ToUpper());

                    sqlCommandText.AppendLine(markText);
                    sqlCommandText.AppendLine("COMMIT TRANSACTION");
                    string commandText = sqlCommandText.ToString();
                    using (SqlConnection sqlConn = new SqlConnection(dbConnectionString))
                    {
                        sqlConn.Open();
                        using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConn))
                        {
                            sqlCommand.ExecuteNonQuery();
                        }
                    }


                });
            }
        }

        #endregion

        #region AppendTextToSharePointNVARCHARMAXUserDataColumn

        public static void AppendTextToSharePointNVARCHARMAXUserDataColumn(string textToAppend, Guid siteId, Guid listId, int itemId, SPField sharePointField)
        {
            XmlDocument schemaXML = new XmlDocument();
            schemaXML.LoadXml(sharePointField.SchemaXml);
            XmlNode fieldNode = schemaXML.SelectSingleNode("/Field");
            XmlAttribute columnNameAttribute = fieldNode.Attributes["ColName"];
            AppendTextToSharePointNVARCHARMAXUserDataColumn(textToAppend, siteId, listId, itemId, columnNameAttribute.InnerText);
        }

        public static void AppendTextToSharePointNVARCHARMAXUserDataColumn(string textToAppend, Guid siteId, Guid listId, int itemId, string ntextColumnName)
        {
            if (textToAppend.Length > 512 || -1 != textToAppend.IndexOf("'"))
            {
                throw new ArgumentException("Invald textToAppend value or format");
            }
            using (SPSite currentSite = new SPSite(siteId))
            {

                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    string dbConnectionString = currentSite.ContentDatabase.DatabaseConnectionString;
                    try
                    {
                        StringBuilder sqlCommandText = new StringBuilder();
                        sqlCommandText.AppendLine("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
                        sqlCommandText.AppendLine("BEGIN TRANSACTION");

                        string markText = SQL_APPEND_NVARCHARMAX.Replace("%COLNAME%", ntextColumnName).Replace("%SMALLTEXTSTRING%", textToAppend);
                        markText = markText.Replace("%ITEMID%", itemId.ToString()).Replace("%LISTID%", listId.ToString().ToUpper());
                        markText = markText.Replace("%SMALLTEXTLENGTH%", textToAppend.Length.ToString()).Replace("%OFFSET%", "NULL");

                        sqlCommandText.AppendLine(markText);
                        sqlCommandText.AppendLine("COMMIT TRANSACTION");
                        string commandText = sqlCommandText.ToString();
                        using (SqlConnection sqlConn = new SqlConnection(dbConnectionString))
                        {
                            sqlConn.Open();
                            using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConn))
                            {
                                sqlCommand.ExecuteNonQuery();
                            }
                        }
                    }
                    catch (SqlException)
                    {
                        //if the column is NULL, meaning the field was added after the content was created, then the .WRITE function will fail
                        //so we will do it the "slow way", this is ok because it only happens the first time, after that the optimized
                        //.WRITE appends to the column
                        StringBuilder sqlCommandText = new StringBuilder();
                        sqlCommandText.AppendLine("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
                        sqlCommandText.AppendLine("BEGIN TRANSACTION");
                        string markText = SQL_CONCAT_NVARCHARMAX.Replace("%COLNAME%", ntextColumnName).Replace("%COLNAME%", ntextColumnName).Replace("%SMALLTEXTSTRING%", textToAppend);
                        markText = markText.Replace("%ITEMID%", itemId.ToString()).Replace("%LISTID%", listId.ToString().ToUpper());
                        sqlCommandText.AppendLine(markText);
                        sqlCommandText.AppendLine("COMMIT TRANSACTION");
                        string commandText = sqlCommandText.ToString();
                        using (SqlConnection sqlConn = new SqlConnection(dbConnectionString))
                        {
                            sqlConn.Open();
                            using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConn))
                            {
                                sqlCommand.ExecuteNonQuery();
                            }
                        }
                    }
                });
            }
        }
        #endregion

        #region ReadNVARCHARMAXDirectlyFromSharePointUserDataColumn
        public static String ReadNVARCHARMAXDirectlyFromSharePointUserDataColumn(Guid siteId, Guid listId, int itemId, SPField sharePointField)
        {
            XmlDocument schemaXML = new XmlDocument();
            schemaXML.LoadXml(sharePointField.SchemaXml);
            XmlNode fieldNode = schemaXML.SelectSingleNode("/Field");
            XmlAttribute columnNameAttribute = fieldNode.Attributes["ColName"];
            return ReadNVARCHARMAXDirectlyFromSharePointUserDataColumn(siteId, listId, itemId, columnNameAttribute.InnerText);
        }

        public static String ReadNVARCHARMAXDirectlyFromSharePointUserDataColumn(Guid siteId, Guid listId, int itemId, string ntextColumnName)
        {
            string returnValue = string.Empty;
            using (SPSite currentSite = new SPSite(siteId))
            {
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    string dbConnectionString = currentSite.ContentDatabase.DatabaseConnectionString;
                    //this is not the technically correct way to read a large nText column however
                    //it will pull back however it works fine and still pages correctly
                    string commandText = SQL_READ_NVARCHARMAX.Replace("%COLNAME%", ntextColumnName).Replace("%ITEMID%", itemId.ToString());
                    commandText = commandText.Replace("%LISTID%", listId.ToString().ToUpper());
                    using (SqlConnection sqlConn = new SqlConnection(dbConnectionString))
                    {
                        sqlConn.Open();
                        using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConn))
                        {
                            SqlDataReader sqlReader = sqlCommand.ExecuteReader();
                            if (sqlReader.Read())
                            {
                                returnValue = (string)sqlReader[0];
                            }
                        }
                    }
                });

            }
            return returnValue.ToString();
        }
        #endregion

        #region Folder and thread query routines

        public static List<SPListItem> FindAllItemPeersInFolder(SPListItem itemInFolder)
        {
            return FindAllItemPeersInFolder(itemInFolder, null);
        }

        public static List<SPListItem> FindAllItemPeersInFolder(SPListItem itemInFolder, string viewFields)
        {
            //If the item is a folder then return its contents
            if (null != itemInFolder.Folder)
            {
                return FindAllItemsInFolder(itemInFolder.Folder, itemInFolder.ParentList, viewFields);
            }
            else
            {
                //Build a query for all non folder items with the same FileDirRef
                List<SPListItem> returnValue = new List<SPListItem>();
                try
                {

                    string fileDirRef = (string)itemInFolder["FileDirRef"];
                    if (-1 != fileDirRef.IndexOf(";#")) fileDirRef = fileDirRef.Substring(fileDirRef.IndexOf(";#") + 2);
                    System.Text.StringBuilder queryXML = new StringBuilder();
                    queryXML.Append("<Where><And><Neq><FieldRef Name='ContentType' /><Value Type='Text'>Folder</Value></Neq>");
                    queryXML.Append("<Eq><FieldRef Name=\"FileDirRef\" /><Value Type=\"Lookup\">");
                    queryXML.Append(fileDirRef);
                    queryXML.Append("</Value></Eq>");
                    queryXML.Append("</And></Where>");
                    SPQuery folderQuery = new SPQuery();
                    folderQuery.Query = queryXML.ToString();

                    folderQuery.ViewAttributes = "Scope=\"RecursiveAll\"  ModerationType=\"Moderator\"";
                    if (!string.IsNullOrEmpty(viewFields))
                    {
                        folderQuery.ViewFields = viewFields;
                    }

                    folderQuery.RowLimit = 100;
                    do
                    {
                        //bug fix, implement paging for lots of results 
                        SPListItemCollection pageResults = itemInFolder.ParentList.GetItems(folderQuery);
                        foreach (SPListItem listItem in pageResults)
                        {
                            returnValue.Add(listItem);
                        }

                    } while (folderQuery.ListItemCollectionPosition != null);

                    if (returnValue.Count < 1) returnValue = null;
                }
                catch (Exception queryError)
                {
                    System.Diagnostics.Trace.WriteLine(queryError.Message);
                    returnValue = null;
                }
                return returnValue;

            }
        }

        public static List<SPListItem> FindAllItemsInFolder(SPFolder folderToSearch, SPList listToSearch)
        {
            return FindAllItemsInFolder(folderToSearch, listToSearch, null);
        }

        public static List<SPListItem> FindAllItemsInFolder(SPFolder folderToSearch, SPList listToSearch, string viewFields)
        {
            List<SPListItem> returnValue = new List<SPListItem>();
            try
            {
                SPQuery folderQuery = new SPQuery();
                folderQuery.Query = "<Where><Neq><FieldRef Name='ContentType' /><Value Type='Text'>Folder</Value></Neq></Where>";
                if (null != folderToSearch)
                {
                    folderQuery.Folder = folderToSearch;
                }
                if (!string.IsNullOrEmpty(viewFields))
                {
                    folderQuery.ViewFields = viewFields;
                }
                folderQuery.RowLimit = 100;
                do
                {
                    //bug fix, implement paging for lots of results 
                    SPListItemCollection pageResults = listToSearch.GetItems(folderQuery);
                    foreach (SPListItem listItem in pageResults)
                    {
                        returnValue.Add(listItem);
                    }

                } while (folderQuery.ListItemCollectionPosition != null);

                if (returnValue.Count < 1) returnValue = null;
            }
            catch (Exception queryError)
            {
                System.Diagnostics.Trace.WriteLine(queryError.Message);
                returnValue = null;
            }
            return returnValue;
        }

        public static List<SPListItem> FindAllItemsInThread(SPListItem threadMember)
        {
            return FindAllItemsInThread(threadMember, null);
        }

        public static List<SPListItem> FindAllItemsInThread(SPListItem threadMember, string viewFields)
        {
            List<SPListItem> returnValue = null;
            try
            {
                if (threadMember.Fields.ContainsField("ParentFolderId"))
                {
                    Int32 parentFolderID = (Int32)threadMember["ParentFolderId"];
                    SPFolder discussionFolder = FindFolderInList(parentFolderID, threadMember.ParentList);
                    returnValue = FindAllItemsInFolder(discussionFolder, threadMember.ParentList, viewFields);
                }
            }
            catch (Exception likelyCastException)
            {
                System.Diagnostics.Trace.WriteLine(likelyCastException.Message);
                returnValue = null;
            }
            if (null == returnValue)
            {
                returnValue = FindAllItemPeersInFolder(threadMember, viewFields);
            }

            return returnValue;
        }

        public static SPFolder ParentFolder(SPListItem childItem)
        {
            string fileDirRef = (string)childItem["FileDirRef"];
            return FindFolderInList(fileDirRef, childItem.ParentList);
        }

        public static SPFolder FindFolderInList(string fileDirRef, SPList folderList)
        {
            SPFolder returnValue = null;
            if (ListSupportsFolders(folderList))
            {
                if (-1 != fileDirRef.IndexOf(";#")) fileDirRef = fileDirRef.Substring(fileDirRef.IndexOf(";#") + 2);
                if (fileDirRef.StartsWith("/")) fileDirRef = fileDirRef.Substring(1);
                try
                {
                    System.Text.StringBuilder queryXML = new StringBuilder();
                    queryXML.Append("<Where><And><And><Eq><FieldRef Name='ContentType' /><Value Type='Text'>Folder</Value></Eq>");
                    queryXML.Append("<Eq><FieldRef Name=\"FileLeafRef\" /><Value Type=\"Lookup\">");
                    queryXML.Append(fileDirRef.Substring(fileDirRef.LastIndexOf('/') + 1));
                    queryXML.Append("</Value></Eq></And>");
                    queryXML.Append("<Eq><FieldRef Name=\"FileDirRef\" /><Value Type=\"Lookup\">");
                    queryXML.Append(fileDirRef.Substring(0, fileDirRef.LastIndexOf('/')));
                    queryXML.Append("</Value></Eq>");
                    queryXML.Append("</And></Where>");
                    SPQuery folderQuery = new SPQuery();
                    folderQuery.Query = queryXML.ToString();
                    folderQuery.ViewAttributes = "Scope=\"RecursiveAll\"  ModerationType=\"Moderator\"";
                    folderQuery.RowLimit = 1;
                    SPListItemCollection foundItems = folderList.GetItems(folderQuery);
                    if (foundItems.Count > 0)
                    {
                        returnValue = foundItems[0].Folder;
                    }
                }
                catch (Exception queryError)
                {
                    System.Diagnostics.Trace.WriteLine(queryError.Message);
                    returnValue = null;
                }
                if (null == returnValue)
                {
                    //This is a hold over for when the schema is customized and the content type of the folder
                    //works for the API but is not of content type Folder
                    foreach (SPListItem folderItem in folderList.Folders)
                    {
                        string fileRef = (string)folderItem["FileRef"];
                        if (-1 != fileRef.IndexOf(";#")) fileRef = fileRef.Substring(fileRef.IndexOf(";#") + 2);
                        if (0 == fileRef.IndexOf('/')) fileRef = fileRef.Substring(1);
                        if (0 == string.Compare(fileRef, fileDirRef, StringComparison.InvariantCultureIgnoreCase))
                        {
                            returnValue = folderItem.Folder;
                            break;
                        }
                    }
                }
            }
            return returnValue;
        }

        public static SPFolder FindFolderInList(Int32 folderId, SPList folderList)
        {
            SPFolder returnValue = null;
            if (ListSupportsFolders(folderList))
            {
                try
                {
                    System.Text.StringBuilder queryXML = new StringBuilder();
                    queryXML.Append("<Where><And><Eq><FieldRef Name='ContentType' /><Value Type='Text'>Folder</Value></Eq>");
                    queryXML.Append("<Eq><FieldRef Name=\"ID\" /><Value Type=\"Counter\">");
                    queryXML.Append(folderId.ToString(System.Globalization.CultureInfo.InvariantCulture));
                    queryXML.Append("</Value></Eq>");
                    queryXML.Append("</And></Where>");

                    SPQuery folderQuery = new SPQuery();
                    folderQuery.ViewAttributes = "Scope=\"RecursiveAll\"  ModerationType=\"Moderator\"";
                    folderQuery.RowLimit = 1;
                    SPListItemCollection foundItems = folderList.GetItems(folderQuery);
                    if (foundItems.Count > 0)
                    {
                        returnValue = foundItems[0].Folder;
                    }
                }
                catch (Exception queryError)
                {
                    System.Diagnostics.Trace.WriteLine(queryError.Message);
                    returnValue = null;
                }
                ////try it the slow way, 
                //if (null == returnValue)
                //{
                //    foreach (SPListItem folderItem in folderList.Folders)
                //    {
                //        if (folderId == folderItem.ID)
                //        {
                //            returnValue = folderItem.Folder;
                //            break;
                //        }
                //    }
                //}
            }

            return returnValue;
        }

        public static bool ListSupportsFolders(SPList listToEvaluate)
        {
            if (listToEvaluate.ServerTemplateCanCreateFolders) return true;
            if (listToEvaluate.BaseTemplate == SPListTemplateType.DiscussionBoard) return true;
            return false;
        }
        #endregion

        #region SPQuery methods

        public static int CountOfPeerItemsAndSubFolders(SPListItem itemInFolder)
        {
            int returnValue = 0;
            try
            {

                string fileDirRef = (string)itemInFolder["FileDirRef"];
                if (-1 != fileDirRef.IndexOf(";#")) fileDirRef = fileDirRef.Substring(fileDirRef.IndexOf(";#") + 2);
                System.Text.StringBuilder queryXML = new StringBuilder();
                queryXML.Append("<Where><Eq><FieldRef Name=\"FileDirRef\" /><Value Type=\"Lookup\">");
                queryXML.Append(fileDirRef);
                queryXML.Append("</Value></Eq></Where>");
                SPQuery folderQuery = new SPQuery();
                folderQuery.Query = queryXML.ToString();
                folderQuery.ViewAttributes = "Scope=\"RecursiveAll\"  ModerationType=\"Moderator\"";
                folderQuery.ViewFields = "<FieldRef Name=\"ID\"/>";
                folderQuery.RowLimit = 100;
                do
                {
                    //bug fix, implement paging for lots of results 
                    SPListItemCollection pageResults = itemInFolder.ParentList.GetItems(folderQuery);
                    foreach (SPListItem listItem in pageResults)
                    {
                        returnValue++;
                    }

                } while (folderQuery.ListItemCollectionPosition != null);


            }
            catch (Exception queryError)
            {
                System.Diagnostics.Trace.WriteLine(queryError.Message);
                return 0;
            }
            return returnValue;
        }

        public static int CountOfPeerReadItemsAndSubFolders(SPListItem itemInFolder, string fieldName, int userId)
        {
            int returnValue = 0;
            try
            {
                string readMark = ReadUnreadField.READ_MARK_DELIMITER + userId.ToString() + ReadUnreadField.READ_MARK_DELIMITER;
                string fileDirRef = (string)itemInFolder["FileDirRef"];
                if (-1 != fileDirRef.IndexOf(";#")) fileDirRef = fileDirRef.Substring(fileDirRef.IndexOf(";#") + 2);
                System.Text.StringBuilder queryXML = new StringBuilder();
                queryXML.Append("<Where><And><Eq><FieldRef Name=\"FileDirRef\" /><Value Type=\"Lookup\">");
                queryXML.Append(fileDirRef);
                queryXML.Append("</Value></Eq><Contains><FieldRef Name=\"");
                queryXML.Append(fieldName);
                queryXML.Append("\" /><Value Type=\"Text\">");
                queryXML.Append(readMark);
                queryXML.Append("</Value></Contains></And></Where>");

                SPQuery folderQuery = new SPQuery();
                folderQuery.Query = queryXML.ToString();
                folderQuery.ViewAttributes = "Scope=\"RecursiveAll\"  ModerationType=\"Moderator\"";
                folderQuery.ViewFields = "<FieldRef Name=\"ID\"/>";
                folderQuery.RowLimit = 100;
                do
                {
                    //bug fix, implement paging for lots of results 
                    SPListItemCollection pageResults = itemInFolder.ParentList.GetItems(folderQuery);
                    foreach (SPListItem listItem in pageResults)
                    {
                        returnValue++;
                    }

                } while (folderQuery.ListItemCollectionPosition != null);


            }
            catch (Exception queryError)
            {
                System.Diagnostics.Trace.WriteLine(queryError.Message);
                return 0;
            }
            return returnValue;
        }

        #endregion

        #region Save item

        //This is a simple wrapper around SPItemEventReceiver
        //that lets us call into the event receiver event manager
        //which in turn calls some functions that manipulate
        //thread local storage to set flags for this thread
        //about the state of event propagation
        internal class EventBypass : SPItemEventReceiver
        {
            //Since the SharePoint event manager uses ThreadLocalStorage for this
            //flag, I am a bit concerned about using a "global static" but for now
            //it works, probably should be load tested.
            public bool EventFiring
            {
                get
                {
                    return base.EventFiringEnabled;
                }

                set
                {
                    if (value != base.EventFiringEnabled)
                    {
                        base.EventFiringEnabled = value;
                    }
                }
            }
        }
        private static EventBypass g_EventManager = new EventBypass();

        public static void WriteReadUnreadMarkValueFieldToSPListItemIgnoringSecurityWithoutFiringEvents(ReadUnreadFieldValue fieldValue, string fieldName, Guid itemId, Guid listId, Guid webId, Guid siteId)
        {
            //If we dont have permission to write the item (e.g. we are a reader)
            //then we need to do the update in an elevated security context
            //We dont want to trigger any events, synchronous or otherwise so
            //we use the SaveSPListItemWithoutFiringEvents wrapper to
            //save the item without notifying the event queues.
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite currentSite = new SPSite(siteId))
                {
                    using (SPWeb currentWeb = currentSite.AllWebs[webId])
                    {
                        bool currentUnsafeSetting = currentWeb.AllowUnsafeUpdates;
                        currentWeb.AllowUnsafeUpdates = true;
                        SPList currentList = currentWeb.Lists[listId];
                        SPListItem currentItem = currentList.GetItemByUniqueId(itemId);
                        currentItem[fieldName] = fieldValue;
                        SaveSPListItemWithoutFiringEvents(currentItem, true);
                        currentWeb.AllowUnsafeUpdates = currentUnsafeSetting;
                    }
                }
            });
        }

        public static void SaveSPListItemWithoutFiringEvents(SPListItem itemToSave)
        {
            SaveSPListItemWithoutFiringEvents(itemToSave, false, false);
        }

        public static void SaveSPListItemWithoutFiringEvents(SPListItem itemToSave, bool systemUpdate)
        {
            SaveSPListItemWithoutFiringEvents(itemToSave, false, true);
        }

        public static void SaveSPListItemWithoutFiringEvents(SPListItem itemToSave, bool systemUpdate, bool incrementListItemVersion)
        {
            //As it turns out, the DisableEventFiring and EnableEventFiring functions in
            //an event handler call a static global manager that uses thread local storage
            //to set flags that the unmanaged codebase recognizes. So a simple event base wrapper 
            //lets us access the static functions that manage event control for the current thread.
            g_EventManager.EventFiring = false;
            try
            {
                if (systemUpdate)
                {
                    itemToSave.SystemUpdate(incrementListItemVersion);
                }
                else
                {
                    itemToSave.SystemUpdate();
                }
            }
            finally
            {
                g_EventManager.EventFiring = true;

            }
        }
        #endregion

        #region InitializeOrUpdateItem
        internal static void InitializeOrUpdateItem(string userName, SPListItem listItem, ReadUnreadField readUnreadField)
        {
            SPUser wssUser = EnsureUser(userName);
            InitializeOrUpdateItem(wssUser.ID, listItem, readUnreadField);
        }

        internal static void InitializeOrUpdateItem(SPUser wssUser, SPListItem listItem, ReadUnreadField readUnreadField)
        {
            InitializeOrUpdateItem(wssUser.ID, listItem, readUnreadField);
        }

        internal static void InitializeOrUpdateItem(int userId, SPListItem listItem, ReadUnreadField readUnreadField)
        {

            if (UtilityMethods.WriteDirect())
            {
                string readMark = ReadUnreadField.READ_MARK_DELIMITER + userId.ToString() + ReadUnreadField.READ_MARK_DELIMITER;
                SetTextExactToSharePointNVARCHARMAXUserDataColumn(readMark,
                                          listItem.ParentList.ParentWeb.Site.ID,
                                          listItem.ParentList.ID,
                                          listItem.ID,
                                          readUnreadField);

            }
            else
            {
                ReadUnreadFieldValue readMarksValue = new ReadUnreadFieldValue();
                readMarksValue.MarkUserRead(userId);
                WriteReadUnreadMarkValueFieldToSPListItemIgnoringSecurityWithoutFiringEvents(readMarksValue,
                                                                             readUnreadField.InternalName,
                                                                             listItem.UniqueId,
                                                                             listItem.ParentList.ID,
                                                                             listItem.ParentList.ParentWeb.ID,
                                                                             listItem.ParentList.ParentWeb.Site.ID);


            }

            SPFolder parentFolder = UtilityMethods.ParentFolder(listItem);
            if (null != parentFolder)
            {
                InitializeOrUpdateItem(userId, parentFolder.Item, readUnreadField);
            }
        }
        #endregion

        #region Read marking
        internal static void MarkItemAsRead(DocumentItemLocator itemLocator)
        {
            //First ensure the user mark
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite wssSite = new SPSite(itemLocator.SiteId))
                {
                    using (SPWeb wssWeb = wssSite.AllWebs[itemLocator.WebId])
                    {
                        SPUser wssUser;
                        try
                        {
                            SPUser testUser = wssWeb.SiteUsers[itemLocator.UserName];
                            if (null == testUser)
                            {
                                throw new InvalidOperationException();
                            }
                            else
                            {
                                wssUser = testUser;
                            }
                        }
                        catch
                        {
                            //allow unsafe updates
                            bool currentUnsafeSetting = wssWeb.AllowUnsafeUpdates;
                            wssWeb.AllowUnsafeUpdates = true;
                            wssUser = wssWeb.EnsureUser(itemLocator.UserName);
                            wssWeb.Update();
                            wssWeb.AllowUnsafeUpdates = currentUnsafeSetting;
                        }
                        //now get the items to pass in 
                        SPList wssList = wssWeb.Lists[itemLocator.ListId];
                        ReadUnreadField ruMarks = FindFirstFieldOnList(wssList);
                        if (null != ruMarks)
                        {
                            SPListItem wssItem = wssList.GetItemByUniqueId(itemLocator.ItemId);
                            //If we have a field we need to mark it
                            MarkItemAsRead(wssUser, wssItem, ruMarks);
                        }
                    }
                }
            });
        }

        internal static void MarkItemAsRead(string userName, SPListItem listItem, ReadUnreadField readUnreadField)
        {
            SPUser wssUser = EnsureUser(userName);
            MarkItemAsRead(wssUser, listItem, readUnreadField);
        }

        internal static void MarkItemAsRead(SPUser wssUser, SPListItem listItem, ReadUnreadField readUnreadField)
        {
            if (null != listItem.Folder)
            {
                MarkFolderSubTreeAsRead(wssUser, listItem.Folder, readUnreadField);
            }
            MarkItemAsRead(wssUser, listItem, readUnreadField, true);
        }

        private static void MarkFolderSubTreeAsRead(SPUser wssUser, SPFolder topFolder, ReadUnreadField readUnreadField)
        {
            //if we are a folder tree then collect all of the child items and mark them as read
            string viewFieldXML = string.Empty;
            if (!UtilityMethods.WriteDirect()) viewFieldXML = "<FieldRef Name=\"" + readUnreadField.InternalName + "\"/>";
            List<SPListItem> childItems = FindAllItemsInFolder(topFolder, topFolder.Item.ParentList, viewFieldXML);
            if (null != childItems)
            {
                foreach (SPListItem childItem in childItems)
                {
                    MarkItemAsRead(wssUser, childItem, readUnreadField, false);
                }
            }
            if (null != topFolder.SubFolders)
            {
                //dont forget to walk down the tree
                foreach (SPFolder childFolder in topFolder.SubFolders)
                {
                    MarkFolderSubTreeAsRead(wssUser, childFolder, readUnreadField);
                }
            }
            MarkItemAsRead(wssUser, topFolder.Item, readUnreadField, false);
        }

        private static void MarkParentFolderAsRead(SPUser wssUser, SPListItem listItem, ReadUnreadField readUnreadField)
        {
            //find the parent folder, if it exists and mark it
            SPFolder parentFolder = UtilityMethods.ParentFolder(listItem);
            if (null != parentFolder)
            {
                MarkItemAsRead(wssUser, parentFolder.Item, readUnreadField);
            }
        }

        private static void MarkItemAsRead(SPUser wssUser, SPListItem listItem, ReadUnreadField readUnreadField, bool checkHierarchy)
        {
            //collect the value 
            ReadUnreadFieldValue readMarksValue = null;
            try
            {
                readMarksValue = (ReadUnreadFieldValue)listItem[readUnreadField.InternalName];
            }
            catch
            {
                readMarksValue = null;
            }
            if (null == readMarksValue) readMarksValue = new ReadUnreadFieldValue();
            //Depending on the configuration options we will either write directly to the column
            //or call our wrapped item update function

            if (UtilityMethods.WriteDirect())
            {
                string readMark = wssUser.ID.ToString() + ReadUnreadField.READ_MARK_DELIMITER;
                if (readMarksValue.IsEmpty)
                {
                    readMark = ReadUnreadField.READ_MARK_DELIMITER + wssUser.ID.ToString() + ReadUnreadField.READ_MARK_DELIMITER;
                }
                AppendTextToSharePointNVARCHARMAXUserDataColumn(readMark,
                                                          listItem.ParentList.ParentWeb.Site.ID,
                                                          listItem.ParentList.ID,
                                                          listItem.ID,
                                                          readUnreadField);

            }
            else
            {


                readMarksValue.MarkUserRead(wssUser.ID);
                WriteReadUnreadMarkValueFieldToSPListItemIgnoringSecurityWithoutFiringEvents(readMarksValue,
                                                                                             readUnreadField.InternalName,
                                                                                             listItem.UniqueId,
                                                                                             listItem.ParentList.ID,
                                                                                             listItem.ParentList.ParentWeb.ID,
                                                                                             listItem.ParentList.ParentWeb.Site.ID);


            }

            if (checkHierarchy)
            {
                //only process the parent tree if the event
                //handler is also wired up

                if (ListSupportsFolders(listItem.ParentList))
                {
                    //If we are in a folder capable list, then we want to find
                    //all the items in the current folder and if they are all
                    //read, then we want to mark the parent folder as read as well
                    //since sharepoint does not have a "Not Contains" query we
                    //will determine the status by checking the count of read
                    //items against the count of actual items. Since the count
                    //needs to include sub folders we cant use the built in collections
                    int readCount = CountOfPeerReadItemsAndSubFolders(listItem,
                                                                     readUnreadField.InternalName,
                                                                     wssUser.ID);
                    int totalCount = CountOfPeerItemsAndSubFolders(listItem);

                    if (totalCount == readCount)
                    {
                        MarkParentFolderAsRead(wssUser, listItem, readUnreadField);
                    }
                }

            }
        }
        #endregion

        #region Unread marking

        public static void MarkItemAsUnread(SPUser wssUser, SPListItem listItem, ReadUnreadField readUnreadField)
        {
            //In our public method, if the first requested item is a folder
            //we will mark all of its children as unread as well
            if (null != listItem.Folder)
            {
                MarkFolderSubTreeAsUnread(wssUser, listItem.Folder, readUnreadField);
            }
            MarkItemAsUnread(wssUser, listItem, readUnreadField, true);
        }

        private static void MarkFolderSubTreeAsUnread(SPUser wssUser, SPFolder topFolder, ReadUnreadField readUnreadField)
        {
            //if we are a folder tree then collect all of the child items and mark them as read
            string viewFieldXML = string.Empty;
            if (!UtilityMethods.WriteDirect()) viewFieldXML = "<FieldRef Name=\"" + readUnreadField.InternalName + "\"/>";
            List<SPListItem> childItems = FindAllItemsInFolder(topFolder, topFolder.Item.ParentList, viewFieldXML);
            if (null != childItems)
            {
                foreach (SPListItem childItem in childItems)
                {
                    MarkItemAsUnread(wssUser, childItem, readUnreadField, false);
                }
            }
            if (null != topFolder.SubFolders)
            {
                //dont forget to walk down the tree
                foreach (SPFolder childFolder in topFolder.SubFolders)
                {
                    MarkFolderSubTreeAsUnread(wssUser, childFolder, readUnreadField);
                    MarkItemAsUnread(wssUser, childFolder.Item, readUnreadField, false);
                }
            }
        }

        private static void MarkParentFolderAsUnread(SPUser wssUser, SPListItem listItem, ReadUnreadField readUnreadField)
        {
            //find the parent folder, if it exists and mark it
            SPFolder parentFolder = UtilityMethods.ParentFolder(listItem);
            if (null != parentFolder)
            {
                MarkItemAsUnread(wssUser, parentFolder.Item, readUnreadField, true);
            }
        }

        private static void MarkItemAsUnread(SPUser wssUser, SPListItem listItem, ReadUnreadField readUnreadField, bool checkHierarchy)
        {
            //Similar routine to mark as read, except reversing
            //the decision. The event handler has its own routines
            //because it actually clears the read marks for all but
            //the editing user.
            if (UtilityMethods.WriteDirect())
            {
                string readMark = ReadUnreadField.READ_MARK_DELIMITER + wssUser.ID.ToString() + ReadUnreadField.READ_MARK_DELIMITER;
                UtilityMethods.RemoveTextFromSharePointNVARCHARMAXUserDataColumn(readMark,
                                                                           listItem.ParentList.ParentWeb.Site.ID,
                                                                           listItem.ParentList.ID,
                                                                           listItem.ID,
                                                                           readUnreadField);

            }
            else
            {
                ReadUnreadFieldValue readMarksValue = (ReadUnreadFieldValue)listItem[readUnreadField.InternalName];
                if (null == readMarksValue) readMarksValue = new ReadUnreadFieldValue();
                readMarksValue.MarkUserUnread(wssUser.ID);
                WriteReadUnreadMarkValueFieldToSPListItemIgnoringSecurityWithoutFiringEvents(readMarksValue,
                                                                                             readUnreadField.InternalName,
                                                                                             listItem.UniqueId,
                                                                                             listItem.ParentList.ID,
                                                                                             listItem.ParentList.ParentWeb.ID,
                                                                                             listItem.ParentList.ParentWeb.Site.ID);
            }

            if (checkHierarchy)
            {

                if (ListSupportsFolders(listItem.ParentList))
                {
                    MarkParentFolderAsUnread(wssUser, listItem, readUnreadField);
                }
            }
        }

        #endregion

        #region Field locator
        public static List<ReadUnreadField> FindFieldsOnList(SPList sourceList)
        {
            List<ReadUnreadField> returnValue = new List<ReadUnreadField>(); ;
            foreach (SPField tempField in sourceList.Fields)
            {
                ReadUnreadField tempCast = tempField as ReadUnreadField;
                if (null != tempCast) returnValue.Add(tempCast);
            }
            return returnValue;
        }

        public static ReadUnreadField FindFirstFieldOnList(SPList sourceList)
        {
            ReadUnreadField returnValue = null;
            foreach (SPField tempField in sourceList.Fields)
            {
                returnValue = tempField as ReadUnreadField;
                if (null != returnValue) break;
            }
            return returnValue;
        }
        #endregion

        #region Ensure User
        static internal SPUser EnsureUser(DocumentItemLocator itemLocator)
        {
            SPUser returnValue = null;
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite currentSite = new SPSite(itemLocator.SiteId))
                {
                    using (SPWeb currentWeb = currentSite.AllWebs[itemLocator.WebId])
                    {
                        try
                        {
                            SPUser testUser = currentWeb.SiteUsers[itemLocator.UserName];
                            if (null == testUser)
                            {
                                throw new InvalidOperationException();
                            }
                            else
                            {
                                returnValue = testUser;
                            }
                        }
                        catch
                        {
                            //allow unsafe updates
                            bool currentUnsafeSetting = currentWeb.AllowUnsafeUpdates;
                            currentWeb.AllowUnsafeUpdates = true;
                            returnValue = currentWeb.EnsureUser(itemLocator.UserName);
                            currentWeb.Update();
                            currentWeb.AllowUnsafeUpdates = currentUnsafeSetting;
                        }
                    }
                }
            });
            return returnValue;
        }

        static internal SPUser EnsureUser(string loginName, SPWeb userWeb)
        {
            //clean up SharePoint forms name
            if (loginName.IndexOf('|') > -1)
            {
                string[] nameParts = loginName.Split('|');
                loginName = nameParts[nameParts.GetUpperBound(0)];
            }
            SPUser returnValue = null;
            try
            {
                returnValue = userWeb.AllUsers[loginName];
            }
            catch
            {
                returnValue = null;
            }
            if (null == returnValue)
            {
                Guid siteID = userWeb.Site.ID;
                Guid webId = userWeb.ID;
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    using (SPSite currentSite = new SPSite(siteID))
                    {
                        using (SPWeb currentWeb = currentSite.AllWebs[webId])
                        {
                            //allow unsafe updates
                            bool currentUnsafeSetting = currentWeb.AllowUnsafeUpdates;
                            currentWeb.AllowUnsafeUpdates = true;
                            returnValue = currentWeb.EnsureUser(loginName);
                            currentWeb.Update();
                            currentWeb.AllowUnsafeUpdates = currentUnsafeSetting;
                        }
                    }
                });

            }
            return returnValue;
        }

        static internal SPUser EnsureUser(string loginName)
        {
            return EnsureUser(loginName, SPContext.Current.Web);
        }
        #endregion

        #region Global settings
        static ReadUnreadFarmSettings g_GlobalSettings = FarmSettings();
        static object g_LockProxy = new object();

        internal static bool WriteDirect()
        {
            bool returnValue = false;
            try
            {
                returnValue = g_GlobalSettings.WriteDirect;
            }
            catch
            {
                //We land here for dirty reads
                g_GlobalSettings = FarmSettings();
                if (null != g_GlobalSettings)
                {
                    returnValue = g_GlobalSettings.WriteDirect;
                }

            }
            return returnValue;
        }

        internal static bool TrackOfficeDocuments()
        {
            bool returnValue = false;
            try
            {
                returnValue = g_GlobalSettings.TrackOfficeDocuments;
            }
            catch
            {
                //We land here for dirty reads
                g_GlobalSettings = FarmSettings();
                if (null != g_GlobalSettings)
                {
                    returnValue = g_GlobalSettings.TrackOfficeDocuments;
                }

            }
            return returnValue;
        }

        internal static string THREAD_COUNT_APP_SETTING_NAME = "ReadUnreadBackgroundThreadCount";
        internal static int DocumentTrackerBackgroundThreadCount()
        {
            int returnValue = 0;
            try
            {
                returnValue = g_GlobalSettings.DocumentTrackerBackgroundThreadCount;
            }
            catch
            {
                //We land here for dirty reads
                g_GlobalSettings = FarmSettings();
                if (null != g_GlobalSettings)
                {
                    returnValue = g_GlobalSettings.DocumentTrackerBackgroundThreadCount;
                }

            }
            return returnValue;
        }

        internal static bool TrackDocuments()
        {
            bool returnValue = false;
            try
            {
                returnValue = g_GlobalSettings.TrackDocuments;
            }
            catch
            {
                //We land here for dirty reads
                g_GlobalSettings = FarmSettings();
                if (null != g_GlobalSettings)
                {
                    returnValue = g_GlobalSettings.TrackDocuments;
                }
            }

            return returnValue;
        }

        internal static ReadUnreadFarmSettings FarmSettings()
        {
            if (null == g_LockProxy) g_LockProxy = new object();
            lock (g_LockProxy)
            {
                ReadUnreadFarmSettings returnValue = null;
                returnValue = (ReadUnreadFarmSettings)Microsoft.SharePoint.Administration.SPFarm.Local.GetObject(ReadUnreadFarmSettings.SETTINGS_ID);
                return returnValue;
            }
        }

        internal static bool CurrentConfigurationSupportsListType()
        {
            return CurrentConfigurationSupportsListType(SPContext.Current.List);
        }

        internal static bool CurrentConfigurationSupportsListType(SPList listToCheck)
        {
            bool returnValue = true;
            if (null != listToCheck)
            {
                if (listToCheck.BaseTemplate == SPListTemplateType.DocumentLibrary)
                {
                    if (!UtilityMethods.TrackDocuments() || !UtilityMethods.WriteDirect())
                    {
                        returnValue = false;
                    }
                }
            }
            return returnValue;
        }
        #endregion

        #region Resource files
        internal static string g_ComputedFieldTemplateCAML = GetComputedFieldTemplateCAML();
        static string GetComputedFieldTemplateCAML()
        {
            string returnValue = string.Empty;
            System.Reflection.Assembly thisAssembly = System.Reflection.Assembly.GetExecutingAssembly();
            using (System.IO.StreamReader fileStream = new System.IO.StreamReader(thisAssembly.GetManifestResourceStream("Voodoo.SharePoint.ReadUnreadMarks.ComputedFieldTemplateCAML.xml")))
            {
                returnValue = fileStream.ReadToEnd();
            }
            return returnValue;
        }
        #endregion

        #region Debug
        internal static void DebugPause(string eventName)
        {
            string debugMessage = "Connect to process #";
            debugMessage += System.Diagnostics.Process.GetCurrentProcess().Id.ToString();
            System.Windows.Forms.MessageBox.Show(debugMessage, "DEBUG-" + eventName,
                                                 System.Windows.Forms.MessageBoxButtons.OK,
                                                 System.Windows.Forms.MessageBoxIcon.Exclamation,
                                                 System.Windows.Forms.MessageBoxDefaultButton.Button1,
                                                 System.Windows.Forms.MessageBoxOptions.ServiceNotification);
            System.Diagnostics.Trace.WriteLine(debugMessage);
        }
        #endregion
    }
}
